package cz.uhk.max.matrix.impl;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.List;

import cz.uhk.max.exceptions.EquationException;
import cz.uhk.max.matrix.Col;
import cz.uhk.max.matrix.Matrix;
import cz.uhk.max.matrix.IMatrixDao;
import cz.uhk.max.matrix.Row;
import cz.uhk.max.matrix.TreeMatrix;
import cz.uhk.max.matrix.TreeType;

public class EquationImpl implements cz.uhk.max.matrix.IEquation {
	private Matrix A;
	private Matrix B;
	private IMatrixDao dao;
	private Long infinite;

	/**
	 * Constructor create an instance of EquationImpl and define two matrices to
	 * next computing and max number (infinite), that occurs in one of matrices.
	 * Matrices A and B have to have the same number or rows and columns.
	 * 
	 * @param A
	 *            Matrix A.
	 * @param B
	 *            Matrix B.
	 * @param infinite
	 *            The infinite or max value from these two matrices.
	 */

	public EquationImpl(EquationImpl eq) throws EquationException {
		this.A = eq.getA();
		this.B = eq.getB();
		dao = new MatrixDaoImpl();
		this.setInfinite(eq.getInfinite());
	}

	public EquationImpl(Matrix a, Matrix b, Long infinite)
			throws EquationException {
		this.A = new Matrix(a);
		this.B = new Matrix(b);

		dao = new MatrixDaoImpl();
		this.setInfinite(infinite);
		if (a.getRowsCount() != b.getRowsCount()) {
			throw new EquationException();
		}
		;

		if (a.getColsCount() != b.getColsCount()) {
			throw new EquationException();
		}
		;
	}

	public EquationImpl(Matrix a, Matrix b) throws EquationException {
		this.A = new Matrix(a);
		this.B = new Matrix(b);

		dao = new MatrixDaoImpl();
		this.setInfinite(infinite);
		if (a.getRowsCount() != b.getRowsCount()) {
			throw new EquationException();
		}
		;

		if (a.getColsCount() != b.getColsCount()) {
			throw new EquationException();
		}
		;
	}

	/**
	 * Return maximum vector that can be use to multiply A with B and keep
	 * equation A*x=B*x.
	 * 
	 * @return Object of type Matrix, that values is maximum of vector x,
	 *         A*x=B*x.
	 */
	public Matrix maxX() {
		/*
		 * Declaration of variables.
		 */
		List<Col> maximum;
		Col minOfMaximum;
		Matrix x = new Matrix(A.getColsCount(), 1);
		Matrix x1 = new Matrix(A.getColsCount(), 1);

		/*
		 * At the beginning matrix x(n) has value infinite at each position, x =
		 * (infinite,infinite,infinite, …, infinite).
		 */
		for (int i = 0; i < x.getRowsCount(); i++) {
			x.setCol(i, 0, this.infinite);
		}

		for (int k = 0; k < A.getRowsCount(); k++) {
			/*
			 * Step 1: First find max values from all rows (rows, that not yet
			 * been eliminated in step 4) in matrix A (ai = max(Ai)) and matrix
			 * B(bi = max (Bi)), i - {1, 2, .., m}. You will have list of
			 * maximum from matrices A and B: {a1, a2, .. am, b1, b2, ..bm}.
			 */
			maximum = dao.maximum(A);
			maximum.addAll(dao.maximum(B));

			/*
			 * Step 2: Second find row for start a computing. From maximum
			 * values finded in step 1 find the smallest value (it can be some
			 * ai or bi). Start compute in row with this smallest value.
			 */
			minOfMaximum = dao.minimum(maximum);

			/*
			 * Step 3: For the rows a1 and b1 from step 2 find the solution of
			 * max-min equation. You are finding max values in matrix x1. Matrix
			 * x1 is maximum solution for one row equation.
			 */
			x1 = maxXForOneRow(A.getRows().get(minOfMaximum.getI()), B
					.getRows().get(minOfMaximum.getI()));

			/*
			 * Step 4: You need not compute with row from step 3 in next steps.
			 * Mark it to eliminate it from computing.
			 */
			A.getRows().get(minOfMaximum.getI()).setEliminated(true);
			B.getRows().get(minOfMaximum.getI()).setEliminated(true);

			/*
			 * Step 5: For next steps it is necessary to change values in all
			 * specific columns of matrices A and B before next computing
			 * according to the next rule. For all columns in both matrices that
			 * not yet been eliminated from computing do: If some value in
			 * column of matrix A or B with index j is bigger than value in x1j
			 * then value of this column must be change to value xj, for j = 1,
			 * 2, 3, …, n.
			 * 
			 * 
			 * in algorithm: For each i For each j Begin If some a(i,j) > xj
			 * Then a(i,j) = x1j If some b(i,j) > xj Then b(i,j) = x1j End
			 */
			for (int i = 0; i < A.getRowsCount(); i++) {
				for (int j = 0; j < A.getColsCount(); j++) {
					if (A.getCol(i, j).getValue() > x1.getCol(j, 0).getValue()) {
						A.getCol(i, j).setValue(x1.getCol(j, 0).getValue());
					}
					if (B.getCol(i, j).getValue() > x1.getCol(j, 0).getValue()) {
						B.getCol(i, j).setValue(x1.getCol(j, 0).getValue());
					}
				}
			}

			/*
			 * Step 6: Now put together x1 from step 3 with x. If you are in
			 * step 6 for the first time, associate x1 with opening values in
			 * matrix x (opening values of x = (infinite, infinite, infinite, …,
			 * infinite)). If you are in step 6 second time, associate x1 with
			 * actual values in matrix x. Values in matrix x during computing
			 * will change, so you will get new values in matrix x. Values in
			 * matrix x is changing until the cycle of steps is not finish. Rule
			 * how to change values in matrix x:
			 * 
			 * For each j Begin If x1j < xj Then xj = x1j End If End
			 */
			for (int i = 0; i < x1.getRowsCount(); i++) {
				if (x1.getCol(i, 0).getValue() < x.getCol(i, 0).getValue()) {
					x.getCol(i, 0).setValue(x1.getCol(i, 0).getValue());
				}
			}
		}
		return x;
	}

	public Matrix maxXForOneRow(Row arow, Row brow) {
		/*
		 * Step 3.1: a = max (a1j), for j = 1, 2, 3, …, n b = max (b1j), for j =
		 * 1, 2, 3, …, n
		 */

		Col a = dao.maximum(arow.getCols());
		Col b = dao.maximum(brow.getCols());

		Matrix result = new Matrix(arow.getColsCount(), 1);

		/*
		 * Presumption: a > b
		 */
		Col t;
		Row s;
		if (a.getValue() < b.getValue()) {
			t = new Col(a);
			s = new Row(arow);
			arow = new Row(brow);
			a = new Col(b);
			brow = new Row(s);
			b = new Col(t);
		}

		/*
		 * For each j (for each column in row) Begin If a1j > b Then x1j = b If
		 * a1j <= b Then x1j = infinite End
		 */
		for (int i = 0; i < arow.getColsCount(); i++) {
			if (arow.getCols().get(i).getValue() > b.getValue()) {
				result.getCol(i, 0).setValue(b.getValue());
			} else {
				result.getCol(i, 0).setValue(infinite);
			}
		}
		return result;
	}

	public Matrix getA() {
		return A;
	}

	public void setA(Matrix a) {
		A = a;
	}

	public Matrix getB() {
		return B;
	}

	public void setB(Matrix b) {
		B = b;
	}

	public IMatrixDao getDao() {
		return dao;
	}

	public void setDao(IMatrixDao dao) {
		this.dao = dao;
	}

	public Long getInfinite() {
		return infinite;
	}

	public void setInfinite(Long infinite) {
		this.infinite = infinite;
	}

	public List<Matrix> solutionsWithLowerBoundForOneRow(Row A, Row B,
			Matrix lowerBound) {
		/*
		 * We are finding solution x= (x1, x2, x3, .., xn) with scheduled lower
		 * bound (l <= x).
		 */

		/*
		 * 1.0 First compute a, b, a = Al, b = Bl
		 */
		Col a = dao.multiRowWithVector(A, lowerBound);
		Col b = dao.multiRowWithVector(B, lowerBound);
		Matrix xInit = new Matrix(lowerBound);
		Matrix x;
		List<Matrix> lowestSolutions = new ArrayList<Matrix>();

		/*
		 * 1.1. If a = b Then l is solution.
		 */
		if (a.getValue().equals(b.getValue())) {
			lowestSolutions.add(lowerBound);
			return lowestSolutions;
		} else {
			/*
			 * 1.2 a <> b, if is not lower boundary solution and presumption is
			 * a > b:
			 * 
			 * We have scheduled minimum values of finding x with lower
			 * boundary. For computing we have also scheduled steady values in
			 * matrices A and B. To find solution we can change a or b if we
			 * change values of lower boundary up. Result can be find if we can
			 * change value of b by the help of increase values of lower
			 * boundary (l) because b is dependent on l (b=Bl).
			 */

			/*
			 * 1.2.1 Condition to find some result: there exists some b(i) >=
			 * l(i) and b(i) >= a.
			 */
			if (a.getValue() < b.getValue()) {
				Col tempc = new Col(a);
				a = new Col(b);
				b = new Col(tempc);

				Row tempr = new Row(A);
				A = new Row(B);
				B = new Row(tempr);
			}

			for (int i = 0; i < A.getColsCount(); i++) {
				if (B.getCols().get(i).getValue() >= lowerBound.getCol(i, 0)
						.getValue()
						&& B.getCols().get(i).getValue() >= a.getValue()) {
					x = new Matrix(xInit);
					x.setCol(i, 0, a.getValue());
					lowestSolutions.add(x);
				}
			}
			if (lowestSolutions.size() != 0) {
				return lowestSolutions;
			} else {
				return null;
			}
		}
	}

	
	public Matrix solutionWithUpperBoundForOneRow(Row A, Row B,
			Matrix upperBound) {
		/*
		 * 2.1 We are finding solution x= (x1, x2, x3, .., xn) with scheduled
		 * upper bound (u >= x).
		 */

		/*
		 * 2.1.1 First compute a, b, a = Au, b = Bu
		 */
		Col a = dao.multiRowWithVector(A, upperBound);
		Col b = dao.multiRowWithVector(B, upperBound);

		/*
		 * 2.1.2 At the beginning, values in x, that we are finding, set to
		 * value of upper boundary.
		 */
		Matrix x;
		x = new Matrix(upperBound);

		/*
		 * 2.2. If a = b Then u is solution.
		 */
		if (a.getValue().equals(b.getValue())) {
			return upperBound;
		} else {
			/*
			 * 2.3 If is not upper boundary solution and presumption is a > b:
			 * We have scheduled maximum values with upper boundary. For
			 * computing we have also scheduled steady values in matrices A and
			 * B. To find solution we can change a or b if we change values of
			 * upper boundary down. Result can be find if we can change value of
			 * a by the help of decrease values of upper boundary u because a is
			 * dependent on u (a=Au). We cannot get bigger values in b (b=Bu),
			 * because we cannot increase u we can only try to find lower values
			 * in vector a.
			 */

			/*
			 * 2.3.1 Condition to find some result: exists some a(i) > b and
			 * u(i) > b
			 * 
			 * If a(i) > b and u(i) > b
			 */
			if (a.getValue() < b.getValue()) {
				Col tempc = new Col(a);
				a = new Col(b);
				b = new Col(tempc);

				Row tempr = new Row(A);
				A = new Row(B);
				B = new Row(tempr);
			}

			/*
			 * If a > b
			 */
			for (int i = 0; i < A.getColsCount(); i++) {
				if (A.getCols().get(i).getValue() > b.getValue()
						&& upperBound.getCol(i, 0).getValue() > b.getValue()) {
					x.setCol(i, 0, b.getValue());
				}
			}

			if (dao.isSame(x, upperBound) == false) {
				return x;
			} else {
				return null;
			}

		}
	}
	
	public List<TreeMatrix> solutionsWithLowerBound(Matrix A, Matrix B,
			Matrix lowerBound) {
		List<TreeMatrix> lowestSolutions = new ArrayList<TreeMatrix>();
		
		TreeMatrix ATreeMatrix = new TreeMatrix(A);
		TreeMatrix BTreeMatrix = new TreeMatrix(B);
		TreeMatrix lTreeMatrix = new TreeMatrix(lowerBound);
		TreeMatrix temp;
			
		Matrix a = dao.multiMatrixWithVector(A, lowerBound);
		Matrix b = dao.multiMatrixWithVector(B, lowerBound);
		
		if (dao.isSame(a, b)) {
			System.out.println("EquationImpl: lower bound is solution");
			lTreeMatrix.setEquality(true);
			lowestSolutions.add(lTreeMatrix); 
			return lowestSolutions;
		} else {
			temp = new TreeMatrix(ATreeMatrix,BTreeMatrix, lTreeMatrix);
			if (temp !=null && temp.getMatrices().size() > 0) {
			return temp.getMatrices(); //first matrix is root, it is not suposse to be some solution
			} else {
				return null;
			}
			}
			
	}

	public void orderSides(Matrix bound) {
		// method is ready only for vector bound of typu (1,n)
		Matrix a = dao.multiMatrixWithVector(A, bound);
		Matrix b = dao.multiMatrixWithVector(B, bound);

		for (int i = 0; i < a.getRowsCount(); i++) {
			if (a.getCol(i, 0).getValue() > b.getCol(i, 0).getValue()) {
				dao.changeSide(i, a, b);
			}
		}
	}

	public Matrix solutionWithUpperBound(Matrix A, Matrix B, Matrix upperBound) {
		// TODO Auto-generated method stub
		return null;
	}

}
